package com.dap.dao.ha;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

import javax.sql.DataSource;

import org.slf4j.LoggerFactory;

import com.alibaba.druid.pool.DruidDataSource;


public class HADataSource implements DataSource {
    private static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(HADataSource.class);

    private PrintWriter logWriter;
    private int loginTimeout = 5;

    /** 心跳检测间隔时间 ，秒 */
    private int timeout = 60;

    private final ScheduledExecutorService schedule;


    public HADataSource(DruidDataSource template) throws SQLException {
        String[] urls = template.getUrl().split(",");

        // 数据源总数
        BackendPool.getInstance().setTotalDatasourceCount(urls.length);

        String[] username = template.getUsername().split(",");
        // 用户名相同可以只配一个
        if (username.length == 1) {
            username = new String[urls.length];
            Arrays.fill(username, template.getUsername());
        }
        String[] password = template.getPassword().split(",");
        // 密码相同可以只配一个
        if (password.length == 1) {
            password = new String[urls.length];
            Arrays.fill(password, template.getPassword());
        }

        // 初始化心跳检测线程池
        schedule = Executors.newScheduledThreadPool(urls.length);
        timeout = (int) template.getTimeBetweenEvictionRunsMillis() / 1000;

        boolean initSuccess = false;

        for (int a = 0; a < urls.length; a++) {
            String url = urls[a];

            DruidDataSource realDs = template.cloneDruidDataSource();
            realDs.setUrl(url);
            realDs.setUsername(username[a]);
            realDs.setPassword(password[a]);

            // 初始化
            try {
                realDs.init();
                initSuccess = true;
            } catch (SQLException e) {
                LOGGER.error("Init DataSource [{}] error ", url, e);
            }

            // 设置心跳检测
            realDs.setName(url.substring(0, url.indexOf("?") != -1 ? url.indexOf("?") : url.length()));
            BackendPool.getInstance().putDataSouce(realDs.getName(), realDs);
            schedule.scheduleAtFixedRate(new DBHeartBeat(realDs, realDs.getName(), timeout, realDs.getValidationQuery()), timeout * 2,
                timeout, TimeUnit.SECONDS);
        }

        // 没有一个数据库启动成功
        if (!initSuccess) {
            throw new SQLException("dataSource init error");
        }
    }


    public void close() {
        schedule.shutdown();
        Iterator<Entry<String, DataSource>> map = BackendPool.getInstance().getAliveDatasourceMap().entrySet().iterator();
        while (map.hasNext()) {
            try {
                ((DruidDataSource) map.next().getValue()).close();
            } catch (Exception e) {
                LOGGER.warn("", e);
            }
        }
    }


    @Override
    public Connection getConnection() throws SQLException {
        DruidDataSource ds = (DruidDataSource) BackendPool.getInstance().getAliveDatasource();

        try {
            Connection conn = ds.getConnection();
            return conn;
        } catch (SQLException e) {
            LOGGER.warn("Could not get connection from {} !", ds.getUrl(), e);
            BackendPool.getInstance().removeDataSouce(ds.getName());
            if (BackendPool.getInstance().getAliveDatasourceCount() > 0) {
                return getConnection();
            }
        }

        throw new SQLException(
            "All dataSource had down ! Please try again after " + ds.getTimeBetweenEvictionRunsMillis() + " Millisecond .");
    }


    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return getConnection();
    }


    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return this.logWriter;
    }


    @Override
    public int getLoginTimeout() throws SQLException {
        return this.loginTimeout;
    }


    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {
        this.logWriter = out;
    }


    @Override
    public void setLoginTimeout(int seconds) throws SQLException {
        this.loginTimeout = seconds;
    }


    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        throw new SQLException("only support isWrapperFor()");
    }


    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        throw new SQLException("only support unwrap()");
    }


    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        throw new SQLFeatureNotSupportedException("only support getParentLogger()");
    }

}
